package by.demchenko.sample.dataaccess.impl;

import by.demchenko.sample.dataaccess.base.PersonDao;
import by.demchenko.sample.datamodel.*;
import by.demchenko.sample.datamodel.enums.UserType;
import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.stereotype.Repository;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.Date;
import java.util.List;

@Repository
public class PersonDaoImpl extends AbstractDaoImpl<Integer, Person> implements PersonDao {

    protected PersonDaoImpl() {
        super(Person.class);
    }

    @Override
    public List<Person> getPersonsByType(UserType userType, boolean fetchJoins) {

        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Person> criteria = cBuilder.createQuery(Person.class);
        Root<Person> root = criteria.from(Person.class);

        criteria.select(root);

        if (fetchJoins) {
            root.fetch(Person_.account, JoinType.LEFT);
            root.fetch(Person_.faculty, JoinType.LEFT);
            root.fetch(Person_.schedules, JoinType.LEFT);
            criteria.distinct(true);
        }

        criteria.where(
                cBuilder.equal(
                        root.get(Person_.account).
                                get(Account_.userType), userType
                )
        );

        criteria.orderBy(new OrderImpl(root.get(Person_.lastName)));

        TypedQuery<Person> query = getEm().createQuery(criteria);
        return query.getResultList();

    }

    @Override
    public Person getById(Integer id, boolean fetchAll) {

        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Person> criteria = cBuilder.createQuery(Person.class);
        Root<Person> root = criteria.from(Person.class);

        criteria.select(root);
        if (fetchAll) {
            root.fetch(Person_.account, JoinType.LEFT);
            root.fetch(Person_.faculty, JoinType.LEFT);
            root.fetch(Person_.schedules, JoinType.LEFT);
            criteria.distinct(true);
        }

        ParameterExpression<UserType> ut = cBuilder.parameter(UserType.class);
        criteria.where(cBuilder.equal(root.get(Person_.id), id));
        TypedQuery<Person> query = getEm().createQuery(criteria);
        return query.getSingleResult();

    }

    @Override
    public <C> List<Person> getStudentsByCriteria(C criterion) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Person> criteria = cBuilder.createQuery(Person.class);
        Root<Person> root = criteria.from(Person.class);


        Predicate p1 = null;
        if (criterion instanceof Course) {
            Join courses = root.join("schedules").join("course");
            p1 = cBuilder.equal(courses.get(Course_.id), ((Course) criterion).getId());
        } else if (criterion instanceof Faculty) {
            p1 = cBuilder.equal(root.get(Person_.faculty), criterion);
            //p1 = cBuilder.equal(courses.get(Faculty_.id), ((Faculty)criterion).getId());
        }

        criteria.select(root);
        criteria.where(p1);

        TypedQuery<Person> query = getEm().createQuery(criteria);

        List<Person> results = query.getResultList();
        return results;

    }

    @Override
    public long getCount(UserType userType) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Long> criteria = cBuilder.createQuery(Long.class);
        Root<Person> root = criteria.from(Person.class);

        criteria.select(cBuilder.count(root));
        criteria.where(
                cBuilder.equal(
                        root.get(Person_.account).
                                get(Account_.userType), userType
                )
        );

        TypedQuery<Long> query = getEm().createQuery(criteria);
        return query.getSingleResult();

    }

    @Override
    public boolean isEnrolledInCourse(Person user, Course course, Date startDate) {
        CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();

        CriteriaQuery<Person> criteria = cBuilder.createQuery(Person.class);
        Root<Person> root = criteria.from(Person.class);

        criteria.select(root);
        root.fetch(Person_.schedules, JoinType.LEFT);
        criteria.distinct(true);
        Predicate p1 = cBuilder.equal(root.get(Person_.id), user.getId());
        criteria.where(p1);

        TypedQuery<Person> query = getEm().createQuery(criteria);
        List<Person> resultList = query.getResultList();
        for (Person p : resultList) {
            for (Schedule s : p.getSchedules()) {
                if ((s.getCourse().equals(course)) && (s.getStartDate().equals(startDate))) {
                    return true;
                }
            }
        }
        return false;
     }

}